/*******************************************************************************
 * Copyright (c) 2008 Sylvain Sicard.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v2.1
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Contributors:
 *     Sylvain Sicard - initial API and implementation
 ******************************************************************************/
package fr.jade.resources.joram.wrapper;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;

import javax.jms.TopicConnectionFactory;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import org.objectweb.fractal.api.Component;
import org.objectweb.fractal.api.NoSuchInterfaceException;
import org.objectweb.fractal.api.control.IllegalBindingException;
import org.objectweb.fractal.api.control.IllegalLifeCycleException;
import org.objectweb.fractal.api.control.LifeCycleController;
import org.objectweb.fractal.util.Fractal;
import org.objectweb.joram.client.jms.Topic;
import org.objectweb.joram.client.jms.admin.AdminException;
import org.objectweb.joram.client.jms.admin.AdminModule;
import org.objectweb.joram.client.jms.tcp.TopicTcpConnectionFactory;

import fr.jade.base.JadeBaseComponent;
import fr.jade.fraclite.api.control.NoSuchAttributeException;
import fr.jade.resources.joram.api.JndiInterface;
import fr.jade.resources.joram.api.TopicInterface;
import fr.jade.util.FractalNavigator;
import fr.jade.util.FractalUtil;
import fr.jade.util.JadeException;


public class TopicWrapper extends JadeBaseComponent implements TopicInterface, LifeCycleController {

	private String[] listAtt = new String[] {"readers","writers","jndi"};

	private Topic myTopic;

	private TopicInterface parentRef = null;
	private Hashtable<String, TopicInterface> childRef = new Hashtable<String, TopicInterface>();

	private final String PARENT_PREFIX = "parent";

	private Properties serverConfig;

	/**
	 * Writers
	 */
	private String writers = "";

	/**
	 * Readers
	 */
	private String readers = "";

	private String jndi = "no";

	private boolean started;

	/**
	 * Access to Jndi Context
	 */
	private Context context;

	private void initializeContext() {
		JndiInterface jndiItf = null;
		try {
			jndiItf = (JndiInterface)Fractal.getBindingController(JoramUtils.getServerComponent(self)).lookupFc("jndi");
		} catch (NoSuchInterfaceException e1) {
			e1.printStackTrace();
		} catch (AdminException e1) {
			e1.printStackTrace();
		}
		System.out.println("JNDI: Host " + jndiItf.getHost() + " Port " + jndiItf.getPort());
		Properties jndiprops = new Properties();
		jndiprops.setProperty("java.naming.factory.initial","fr.dyade.aaa.jndi2.client.NamingContextFactory");
		jndiprops.setProperty("java.naming.factory.host", jndiItf.getHost());
		jndiprops.setProperty("java.naming.factory.port", jndiItf.getPort());

		Thread ct = Thread.currentThread();
		ClassLoader cl = ct.getContextClassLoader();
		ct.setContextClassLoader(getClass().getClassLoader());
		try {
			context = new InitialContext(jndiprops);
		} catch (NamingException e) {
			e.printStackTrace();
		}
		ct.setContextClassLoader(cl);
	}

	public Topic getTopic(){
		return myTopic;
	}

	private void setAccessRights() throws Exception {
		String token;
		StringTokenizer st;

		// Set writers
		st = new StringTokenizer(writers);
		while(st.hasMoreTokens()){
			token = st.nextToken();
			if (token.equals("all")){
				myTopic.setFreeWriting();
			}
			else{
				myTopic.setWriter(JoramUtils.getUserInterfaceFromName(self, token).getUser());
			}
		}

		// Set readers
		st = new StringTokenizer(readers);
		while(st.hasMoreTokens()){
			token = st.nextToken();
			if (token.equals("all"))
				myTopic.setFreeReading();
			else
				myTopic.setReader(JoramUtils.getUserInterfaceFromName(self, token).getUser());
		}
	}

	public void setProperties() throws JadeException {
		if (jndi.equals("yes")){
			initializeContext();
			try {
				context.bind(Fractal.getNameController(self).getFcName(), myTopic);
			} catch (NamingException e) {
				e.printStackTrace();
			} catch (NoSuchInterfaceException e) {
				e.printStackTrace();
			}
		}
	}

	private void bindParentJoram() throws JadeException{
		// Set parent if it has one
		if (parentRef != null){
			try {
				TopicConnectionFactory con = TopicTcpConnectionFactory.create(serverConfig.getProperty("hostName"), JoramUtils.PORT_NUMBER_START + Integer.parseInt(serverConfig.getProperty("sid")));
				AdminModule.connect(con, serverConfig.getProperty("userAdmin"), serverConfig.getProperty("passAdmin"));
				Topic t = (Topic) parentRef.getTopic();
				myTopic.setParent(t);
				System.out.println("Parent " + parentRef.getTopic().getName() + " setted for " + FractalNavigator.getName(self) + " in startParent");
				// Memorize children for when we will delete this topic
				parentRef.setChildInHierarchy(Fractal.getNameController(self).getFcName(),(TopicInterface)self.getFcInterface("topic"));
				AdminModule.disconnect();
			} catch (Exception e){
				e.printStackTrace();
				throw new JadeException("Unable to set the parent " + parentRef.getTopic().getName() + " for " + myTopic.getName(),e);
			}
		}
	}

	private void unbindParentJoram() throws JadeException{
		try {
			TopicConnectionFactory con = TopicTcpConnectionFactory.create(serverConfig.getProperty("hostName"), JoramUtils.PORT_NUMBER_START + Integer.parseInt(serverConfig.getProperty("sid")));
			AdminModule.connect(con, serverConfig.getProperty("userAdmin"), serverConfig.getProperty("passAdmin"));
			myTopic.unsetParent();
			AdminModule.disconnect();
			System.out.println(Fractal.getNameController(self).getFcName() + " has removed its parent (Joram level)");
		} catch (Exception e){
			e.printStackTrace();
			throw new JadeException("Unable to remove the parent"); 
		}
	}

	private void initServerConfig() throws NoSuchInterfaceException, AdminException, NoSuchAttributeException, JadeException{
		serverConfig = new Properties();
		Component server = JoramUtils.getServerComponent(self);
//		serverConfig.put("hostName", FractalUtil.getAttributeController(server).getAttribute("host"));
		serverConfig.put("hostName", FractalUtil.getAttributeController(FractalNavigator.getNode(server)).getAttribute("hostName"));
		serverConfig.put("port", JoramUtils.PORT_NUMBER_START + Integer.parseInt(FractalUtil.getAttributeController(server).getAttribute("sid")));
		serverConfig.put("sid",FractalUtil.getAttributeController(server).getAttribute("sid"));
		serverConfig.put("userAdmin", FractalUtil.getAttributeController(server).getAttribute("userAdmin"));
		serverConfig.put("passAdmin", FractalUtil.getAttributeController(server).getAttribute("passAdmin"));
	}

	public void startFc() throws IllegalLifeCycleException {
	  if(!started && JoramUtils.serverIsStarted(self)){
  		try {
  			initServerConfig();
  
  			TopicConnectionFactory con = TopicTcpConnectionFactory.create(serverConfig.getProperty("hostName"), JoramUtils.PORT_NUMBER_START + Integer.parseInt(serverConfig.getProperty("sid")));
  			AdminModule.connect(con, serverConfig.getProperty("userAdmin"), serverConfig.getProperty("passAdmin"));
  			myTopic = Topic.create(Integer.parseInt(serverConfig.getProperty("sid")),Fractal.getNameController(self).getFcName());
  			System.out.println("Topic " + Fractal.getNameController(self).getFcName() + " created (Joram level)");
  			setAccessRights();
  			AdminModule.disconnect();
  			setProperties();
  			started = true;
  		} catch (Exception e) {
  			e.printStackTrace();
  			throw new IllegalLifeCycleException("Error while starting the service");
  		}
	  }
	}

	public void stopFc() throws IllegalLifeCycleException {
	  if(started){
  		try {
  			log("Topic " + Fractal.getNameController(self).getFcName() + " stopping");
  			TopicConnectionFactory con = TopicTcpConnectionFactory.create(serverConfig.getProperty("hostName"), JoramUtils.PORT_NUMBER_START + Integer.parseInt(serverConfig.getProperty("sid")));
  
  			// Unbind parentRef if topic is part of a hierarchical topic
  			unbindFc(PARENT_PREFIX);
  			
  			// Test if this topic had child in hierarchy
  			Enumeration<TopicInterface> children = childRef.elements();
  			while(children.hasMoreElements()){
  				TopicInterface ti = (TopicInterface)children.nextElement();	
  				ti.unsetParent();
  			}
  			
  			// Delete Topic from Joram  legacy
  			AdminModule.connect(con, serverConfig.getProperty("userAdmin"), serverConfig.getProperty("passAdmin"));
  			myTopic.delete();
  			AdminModule.disconnect();
  			
  			// Unbind the Topic from Jndi if necessary
  			if (jndi.equals("yes")){
  				context.unbind(Fractal.getNameController(self).getFcName());
  				log("Topic " + Fractal.getNameController(self).getFcName() + " unbinded from Jndi context");
  			}
  			started = false;
  		} catch (Exception e) {
  			e.printStackTrace();
  			throw new IllegalLifeCycleException("Error while stopping the service");
  		}
  		myTopic = null;
	  }
	}

	public void unsetParent(){
		if(parentRef != null){
			try {
				parentRef = null;
				log(Fractal.getNameController(self).getFcName() + " has unbound its parent (Fractal level)");
				try {
					unbindParentJoram();
				} catch (JadeException e) {
					e.printStackTrace();
				}
			} catch (NoSuchInterfaceException e1) {
				e1.printStackTrace();
			}

		}
	}

	/*public void setParent(String parentName) throws JadeException{
		if(parentRef != null){
			// a topic can only have one parent -> Error if it has already one
			throw new JadeException("The topic already has a parent");
		}
		else{
			try {
				TopicInterface ti = (TopicInterface)getInterfaceInAllServers(JoramElements.topic, parentName);
				bindFc(PARENT_PREFIX, ti);
				bindParentJoram();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}*/

	public String getAttribute(String name) throws NoSuchAttributeException {
		if(name.equals("readers"))
			return readers;
		else if(name.equals("writers"))
			return writers;
		else if(name.equals("jndi"))
			return jndi;
		else
			return super.getAttribute(name);
	}

	public String[] listFcAtt() {
		return super.concat(listAtt, super.listFcAtt());
	}

	public void setAttribute(String name, String value) throws NoSuchAttributeException {
		if(name.equals("readers"))
			this.readers = value;
		else if(name.equals("writers"))
			this.writers = value;
		else if(name.equals("jndi"))
			this.jndi = value;
		else
			super.setAttribute(name, value);
	}

	public void bindFc(String itfName, Object itfValue) throws NoSuchInterfaceException, IllegalBindingException, IllegalLifeCycleException {
		if (itfName.equals(PARENT_PREFIX)){
			parentRef = (TopicInterface)itfValue;
			log("ParentRef binded for " + Fractal.getNameController(self).getFcName());
			try {
				bindParentJoram();
			} catch (JadeException e) {
				e.printStackTrace();
			}
		}else 
		  super.bindFc(itfName, itfValue);
	}

	public String[] listFc() {
	  String[] list = new String[] {"parent"};
		return super.concat(list, super.listFc());
	}

	public Object lookupFc(String itfName) throws NoSuchInterfaceException {
		if (itfName.equals(PARENT_PREFIX))
			return parentRef;
		else
		  return super.lookupFc(itfName);
	}

	public void unbindFc(String itfName) throws NoSuchInterfaceException, IllegalBindingException, IllegalLifeCycleException {
		if (itfName.equals(PARENT_PREFIX)){
			if(parentRef != null){
				parentRef.unsetChildInHierarchy(Fractal.getNameController(self).getFcName());
				parentRef = null;
				log(FractalNavigator.getName(self) + " has unbound its parent (Fractal level)");
				try {
					unbindParentJoram();
				} catch (JadeException e) {
					e.printStackTrace();
				}
			}
		}else
			super.unbindFc(itfName);
	}

	public void setChildInHierarchy(String topicName, TopicInterface ti) {
		childRef.put(topicName, ti);
	}

	public void unsetChildInHierarchy(String topicName) {
		childRef.remove(topicName);
		log(FractalNavigator.getName(self) + " has removed " + topicName + " reference from its sons");
	}

	public String getFcState() {
		return started ? STARTED : STOPPED;
	}
}
